package com.linran.utils.utils;

import com.alibaba.fastjson.JSONObject;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.linran.utils.bean.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.SystemUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * https://www.cnblogs.com/Dreamer-1/p/10394011.html
 */
@Slf4j
public class FFmpegUtil {
    private static final Integer DEFAULT_TIME_OUT = 30;
    private static final String FFMPEG_CMD = "ffmpeg";
    private static final int SUCCESS_CODE = 0;

    /**
     * 预估时长 7s
     * @param originFilePath
     * @param mp3FilePath
     * @return
     */
    public static boolean convertFlacToMp3(String originFilePath, String mp3FilePath){
        List<String> commands = new ArrayList<>();
        //ffmpeg -i aresample.flac -ab 320k -map_metadata 0 -id3v2_version 3 huanri.mp3
        commands.add(FFMPEG_CMD);
        commands.add("-y");
        commands.add("-i");
        commands.add(originFilePath);
        commands.add("-ab");
        commands.add("320k");
        commands.add("-map_metadata");
        commands.add("0");
        commands.add("-id3v2_version");
        commands.add("3");
        commands.add(mp3FilePath);
        return callFFmpegCommand(originFilePath, mp3FilePath, commands, "mp3音频转码");
    }

    /**
     * 预估时长:3s
     */
    public static boolean converFlacTo16bit(String originFilePath, String target16bitPath){
        List<String> commands = new ArrayList<>();
        //ffmpeg -y -i [input.flac] -af aresample=out_sample_fmt=s16:out_sample_rate=48000 [out.flac]
        commands.add(FFMPEG_CMD);
        commands.add("-y");
        commands.add("-i");
        commands.add(originFilePath);
        commands.add("-af");
        commands.add("aresample=out_sample_fmt=s16:out_sample_rate=48000");
        commands.add(target16bitPath);
        return callFFmpegCommand(originFilePath,target16bitPath,commands,"16bit音频转码");
    }

    public static boolean callFFmpegCommand(String originFilePath, String target16bitPath, List<String> commands, String taskName){
        //linux 环境控制ffmpeg执行时长
        if (!SystemUtils.IS_OS_WINDOWS){
            commands.add("/usr/bin/timeout");
            commands.add(String.format("%ss", DEFAULT_TIME_OUT));
        }
        String command = String.join(" ",commands);
        log.info("ffmpeg command:{}", command);
        //调用命令
        long startNanoTime = System.nanoTime();
        Process process = null;
        try {
            ProcessBuilder builder = new ProcessBuilder();
            builder.command(commands);
            log.info("[{}] 开始执行...",taskName);
            process = builder.start();
        } catch (IOException e) {
            throw new BizException("ffmpeg命令执行异常, command: " + command, e);
        }

        //ffmpeg 输出日志打印，防阻塞
        dealStream(process);

        //等待结束
        int waitValue = -1;
        int exitValue = -1;
        try {
            waitValue = process.waitFor();
            exitValue = process.exitValue();
            //log.info("[{}] 执行结果 waitValue:{} exitValue:{}",taskName ,waitValue, exitValue);
        } catch (InterruptedException e) {
            throw new BizException("InterruptedException", e);
        }
        log.info("[{}] 耗时:{} s", taskName,(System.nanoTime() - startNanoTime) / 1000000000.0);

        if (exitValue == SUCCESS_CODE){
            log.info("[{}] 成功, targetFilePath:{}",taskName ,target16bitPath);
            return true;
        } else {
            log.error("[{}] 失败, originAudioPath:{}",taskName ,originFilePath);
            throw new BizException(String.format("%s失败",taskName));
        }
    }

    /**
     * 输出日志打印，防止阻塞
     * @param process
     */
    private static void dealStream(Process process) {
        if (process == null) {
            return;
        }
        ThreadFactory threadNameFormat = new ThreadFactoryBuilder().setNameFormat("ffmpeg-input-stream-%d").build();
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 2, 3, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),threadNameFormat);
        // 处理InputStream的线程
        executor.execute(()->{
            String line = null;
            try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                while ((line = in.readLine()) != null) {
                    log.info("[ffmpeg msg]" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
        // 处理ErrorStream的线程
        executor.execute(()->{
            String line = null;
            try(BufferedReader err = new BufferedReader(new InputStreamReader(process.getErrorStream()))) {
                while ((line = err.readLine()) != null) {
                    log.info("[ffmpeg msg]" + line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    /**
     * 通过网络地址获取视频时长
     *
     * @param videoUrl
     */
    public static String getVideoDuration(String videoUrl) {
        if (videoUrl==null || "".equals(videoUrl.trim())) {
            log.info("[通过网络视频地址解析时长] videoUrl is null, 返回时长为空");
            return null;
        }

        try {
            String videoInfo = getVideoInfo(videoUrl);
            String duration = getDuration(videoInfo);

            log.info("[通过网络视频地址解析时长] videoUrl: {}, 获取时长: {}", videoUrl, duration);
            return duration;
        } catch (Exception e) {
            log.info("[通过网络视频地址解析时长] 解析异常, 返回时长为空, videoUrl: {}, 异常信息: {}", videoUrl, e.getMessage());
            return null;
        }
    }

    private static String getVideoInfo(String videoUrl) throws IOException, InterruptedException {
        List<String> commands = new ArrayList<>();

        commands.add("D:\\Program Files\\ffmpeg\\bin");
        commands.add(String.format("%ss", 30));

        commands.add("ffmpeg");
        commands.add("-i");
        commands.add(videoUrl);

        ProcessBuilder builder = new ProcessBuilder();
        builder.command(commands);
        Process ps = builder.start();

        // 进程结束 && 执行成功
        int waitFor = ps.waitFor();
        log.info("waitFor: " + waitFor);

        int exitValue = ps.exitValue();
        log.info("exitValue: " + exitValue);

        BufferedReader br = new BufferedReader(new InputStreamReader(ps.getErrorStream()));
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = br.readLine()) != null) {
            sb.append(line).append("\n");
        }

        log.info("[通过ffmpeg解析网络视频地址] videoUrl: {}, result: {}", videoUrl, sb.toString());
        return sb.toString();

    }

    private static String getDuration(String ffmpegResult) {
        String regexDuration = "Duration: (.*?), start: (.*?), bitrate: (\\d*) kb\\/s";
        Pattern pattern = Pattern.compile(regexDuration);
        Matcher m = pattern.matcher(ffmpegResult);

        if (m.find()) {
            String group1 = m.group(1);
            if (group1==null || "".equals(group1.trim())) {
                return null;
            }
            return group1.substring(0, group1.lastIndexOf("."));
        } else {
            return null;
        }
    }

    /**
     * @param
     * @return
     * @description
     */
    public static JSONObject qualityVedio(String path) {
        try {
            ProcessBuilder builder = new ProcessBuilder
                    ("ffprobe", "-v", "quiet", "-print_format", "json", "-show_streams", "-show_format", path);
            Process ps = builder.start();
            // 进程结束 && 执行成功
            int waitFor = ps.waitFor();
            log.info("waitFor: " + waitFor);

            int exitValue = ps.exitValue();
            log.info("exitValue: " + exitValue);
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line).append("\n");
            }
            JSONObject jsonObject = JSONObject.parseObject(sb.toString());
            return jsonObject;
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }
}
